#!/usr/bin/env python
#
# Copyright 2008 Richard J. Bell. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

""" Admin classes for GeoWiki. """

# Python Imports
from django.utils import simplejson as json
import os
import logging

# Google App Engine Imports
from google.appengine.ext import webapp
from google.appengine.ext import db

# Application Imports
from app.gwbaserequesthandler import GWRequestHandler
from app.gwuser import GWUser
from app.gwxml import Xml2Obj

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Tag page support
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class Tag(db.Model):
    """ Standard tags for GeoWIki page features.
    GeoWIki pages have 0 or more geo features.  Each geo feature may have 0 or 
    more tags.  ANY text is permissible in a tag.  This model contains those 
    that are displayed as 'standard predefined tags' during feature editing.
    """
    tag = db.StringProperty( required = True, multiline = False )               # feature tag, may contain spaces
    description = db.StringProperty( required = False, multiline = False )      # description of what the tag means
    @staticmethod
    def eraseAll():
        q = Tag.all()
        for r in q: 
            db.delete( r )
        logging.info('-----> erased all tags');
        return 'Erased all tags.'
    @staticmethod
    def load( overwrite ):
        xml = Xml2Obj()
        report = ''
        root = xml.Parse( 'config/tags.xml' )
        tags = root.getElements( 'tag' )
        for tag in tags:
            name = tag.attributes['name']
            logging.info('-----> load tag %s' % name)
            x = Tag.gql('WHERE tag = :1', name).get()
            if x and overwrite == 'false':
                report += ( '<br />Tag %s exists' % name )
                continue
            description = tag.getElementData('description')
            ntag = Tag( tag = name, description = description )
            ntag.put()
            report += ('<br />Initialize tag %s' % name)
        return report

class TagsEdit(GWRequestHandler):
    def get(self):
        """  """
        if GWUser.isAdmin():
            self.generate( 'admin/tags.html', templateValues={ 'title': 'Edit Tags' })
        else:
            self.generate('admin/adminrequired.html', templateValues={ 'title': 'Admin Required' })
        return

class TagsJSON(GWRequestHandler):
    """ Handle all tag reqests. """
    def get(self):
        """ Returns a JSON string object containing the feature tag list. """
        # Get the tags.
        tags = []
        alltags = Tag.all()
        alltags = alltags.order( '-tag' )
        for tag in alltags:
            tags.append( {'tag':tag.tag, 'description':tag.description} )
        # turn into a JSON object
        jsonObj = {'tags': tags }
        jsonmsg = json.dumps(jsonObj)
        # return it
        self.response.headers['Content-Type'] = 'application/javascript'
        self.response.headers['Cache-Control'] = 'max-age=2592000'              # set cash timeout
        self.response.out.write(jsonmsg)
        return
    def post(self):
        """ Accepts a JSON list of feature tags and updates the datastore. """
        if not GWUser.isAdmin:
            self.generate( 'admin/adminrequired.html', templateValues={ 'title': 'Admin Required' } )
            return
        # delete all existing tags
        db.delete( Tag.all() )
        # save these feature tags
        data = self.request.body
        tagList = json.loads( data )
        for aTag in tagList:
            # make a new tag and save it
            if aTag['tag'] == '':
                continue
            newTag = Tag( tag = aTag['tag'], description = aTag['description'] )
            newTag.put()
        # Return success
        # Here return type application/javascript since browsers will display it 
        # as an aid to debug.  Application/json is generally not displayed.
        self.response.headers['Content-Type'] = 'application/javascript'
        self.response.set_status( 200, "Success saving feature tags" )
        return
    

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# GeoWiki color support
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class Color(db.Model):
    """ Colors for use with GeoWiki line and shape features.
    See Tag.
    """
    name = db.StringProperty( required = True, multiline = False )              # the name
    color = db.StringProperty( required = True, multiline = False )             # the hex color value
    
    @staticmethod
    def dictionary( ):
        """ return a list of all colors """
        colordic = {}
        colors = Color.all()
        colors = colors.order('-name')
        for color in colors:
            colordic[color.name] = color.color
        return colordic
    @staticmethod
    def listAll( ):
        """ return a list of all colors """
        colorlist = []
        colors = Color.all()
        colors = colors.order('-name')
        for color in colors:
            colorlist.append( {'name': color.name, 'color': color.color } )
        return colorlist
    @staticmethod
    def eraseAll():
        q = Color.all()
        for r in q: 
            db.delete( r )
        logging.info('-----> erased all colors');
        return 'Erased all colors.'
    @staticmethod
    def load( overwrite ):
        xml = Xml2Obj()
        report = ''
        root = xml.Parse( 'config/colors.xml' )
        colors = root.getElements( 'color' )
        for color in colors:
            #logging.info('-----> load color %s' % color)
            name = color.attributes['name']
            x = Tag.gql('WHERE color = :1', name).get()
            if x and overwrite == 'false':
                report += ( '<br />Color %s exists' % name )
                continue
            value = color.attributes['color']
            ncolor = Color( name = name, color = value )
            ncolor.put()
            report += ('<br />Initialize color %s=%s' % (name, value))
        return report

class ColorsEdit(GWRequestHandler):
    """ Display HTML add/edit/delete feature tag listing page. """
    def get(self):
        """  """
        if GWUser.isAdmin():
            self.generate( 'admin/colors.html', templateValues={ 'title': 'Colors Edit' } )
        else:
            self.generate('admin/adminrequired.html', templateValues={ 'title': 'Admin Required' })
        return

class ColorsJSON(GWRequestHandler):
    """ Handle all color list request (/gw/json/colors) get and post. """
    def get(self):
        """ Returns a JSON string object containing the colors list. """
        colorlist = Color.listAll()
        jsonObj = {'colors': colorlist }
        jsonmsg = json.dumps(jsonObj)
        self.response.headers['Content-Type'] = 'application/javascript'
        self.response.headers['Cache-Control'] = 'max-age=2592000'              # set cash timeout
        self.response.out.write(jsonmsg)
        return
    def post(self):
        """ Accepts a JSON list of colors and updates the datastore """
        if not GWUser.isAdmin:
            self.generate( 'admin/adminrequired.html', templateValues={ 'title': 'Admin Required' } )
            return
        # delete all existing feature tags
        db.delete( Color.all() )
        # save these colors
        data = self.request.body
        colors = json.loads( data )
        for color in colors:
            # make a color and save it
            if color['name'] == '' or color['color'] == '':
                continue
            newC = Color( name = color['name'], color = color['color'] )
            newC.put()
        # Return success
        # Here return type application/javascript since browsers will display it 
        # as an aid to debug.  Application/json is generally not displayed.
        self.response.headers['Content-Type'] = 'application/javascript'
        self.response.set_status( 200, "Success saving colors" )
        return
            
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Page marker support
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class Marker(db.Model):
    """ Marker images for wiki pages. """
    name = db.StringProperty( required = True )                                 # URI name of the image
    img = db.BlobProperty( required = True )                                    # holds the img (.png, etc.)
    
    @staticmethod
    def listAllNames():
        """ List all marker names. """
        markerlist = []
        markers = Marker.all()
        markers = markers.order('name')
        for marker in markers:
            markerlist.append({'name': marker.name})
        return markerlist
    @staticmethod
    def eraseAll():
        q = Marker.all()
        for r in q: 
            db.delete( r )
        logging.info('-----> erased all markers');
        return 'Erased all markers.'
    @staticmethod
    def load( overwrite ):
        xml = Xml2Obj()
        report = ''
        files = os.listdir( 'markerimages' )
        for marker in files:
            # if not marker, ignour
            logging.info('-----> load marker %s' % marker)
            fName, fExt = os.path.splitext( marker )
            if fExt != '.png' and fExt != '.jpg' and fExt != 'gif': continue
            x = Marker.gql('WHERE name = :1', marker).get()
            if x and overwrite == 'false':
                report += ( '<br />Marker %s exists' % marker )
                continue
            # read the file
            infile = open( 'markerimages/' + marker, 'rb' )
            img = infile.read()
            infile.close()
            n = Marker( name = marker, img = img )
            n.put()
            report += ('<br />Initialize marker %s' % marker)
        return report
    
class MarkersEdit(GWRequestHandler):
    def get(self):
        """ Display the marker page """
        if GWUser.isAdmin():
            self.generate( 'admin/markers.html', templateValues={ 'title': 'Markers Edit' } )
        else:
            self.generate('admin/adminrequired.html', templateValues={ 'title': 'Admin Required' })
        return
            
class MarkersList(GWRequestHandler):
    def get(self):
        jsonmsg = json.dumps({'markers': Marker.listAllNames() })
        self.response.headers['Content-Type'] = 'application/javascript'
        self.response.out.write(jsonmsg)
        
class Markers(GWRequestHandler):
    """ Handle all page marker reqests. """
    def get(self, name):
        """Get a page marker from the data store.""" 
        marker = Marker.gql('WHERE name = :1', name).get()
        if not marker:
            self.redirect('/static/img/notfound.png')
            return
        head, ext = marker.name.split('.')                                      # get extension
        self.response.headers['Content-Type'] = 'image/'+ext.encode()           # return image
        self.response.headers['Cache-Control'] = 'max-age=2592000'              # set cash timeout
        self.response.out.write( marker.img )
    def delete(self, name):
        """ delete the specified marker """
        if not GWUser.isAdmin:
            self.generate( 'admin/adminrequired.html', templateValues={ 'title': 'Admin Required' } )
            return
        marker = Marker.gql('WHERE name = :1', name).get()
        if marker:
            marker.delete()
        self.response.set_status( 200, "Marker deleted" )
        return
    def post(self):
        """ If marker exist, replace it.
            Otherwise, reate a new marker and save in the datastore 
        """
        if not GWUser.isAdmin:
            self.generate( 'admin/adminrequired.html', templateValues={ 'title': 'Admin Required' } )
            return
        # get the name and image
        img = self.request.get("img")
        img = db.Blob(img)
        name, ext = os.path.splitext( self.request.get("name") )
        filename, ext = os.path.splitext( self.request.get("filename") )
        # todo: make sure name is simple (no spaces, only ascii)
        if name == '': name = filename
        name = name.lower() + ext.lower()
        marker = Marker.gql('WHERE name = :1', name).get()
        if marker:
            marker.img = img
            marker.put()
            operation = 'update'
        else:
            newPM = Marker( name = name, img = img)
            newPM.put()
            operation = 'create'
        # Return success
        jsonObj = {'success': True, 'operation': operation, 'name': name }
        jsonmsg = json.dumps(jsonObj)
        # Send the response as text/plain since otherwise the browser may
        # decide it's a file download (IE does on anything/JSON or /javascript
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write(jsonmsg)
        return

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Datastore validation support
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class DatastoreValidate(GWRequestHandler):
    def get(self):
        """ Display the datastore page """
        if not GWUser.isAdmin():
            self.generate('admin/adminrequired.html', templateValues={ 'title': 'Admin Required' })
            return
        
        # todo: need to check each datastore for internal consistency
        # todo: tags unique and legal<br >
        # todo: color names unique and legal<br >
        # todo: color values unique and legal<br >
        # todo: marker names unique and legal URI's<br >
        # todo: wiki names unique and legal<br >        
        # todo: delete any orphaned comments
        # todo: delete any orphaned features
        # todo: check for duplicate pages and fix, should be only one current, it should be most recent
        # todo: check page revision number
        # todo: we need a way to manage how many wiki revisions
        self.generate( 'admin/datastore.html', templateValues={ 'title': 'Datastore Validate' } )
        return

        


#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Developer support
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class Developer(GWRequestHandler):
    """ Display the erase/load page """
    def get(self):
        """ Display the erase/load page """
        if GWUser.isDeveloper():
            self.generate( 'admin/developer.html', templateValues={ 'title': 'Developer Support' } )
        else:
            self.generate('admin/developerrequired.html', templateValues={ 'title': 'Developer Required' })
        return
class DeveloperJson(GWRequestHandler):
    """ Erase/load data into data store """
    def get(self):
        """ DisplErase/load data into data storege """
        from app.gwpage import GWPage, GWFeature, GWComment, GWQuery
        if not GWUser.isDeveloper():
            self.generate('admin/developerrequired.html', templateValues={ 'title': 'Developer Required' })
            return
        # parse the command
        erase = self.request.get('erase')
        load = self.request.get('load')
        overwrite = self.request.get('overwrite')
        obj = self.request.get('object')
        msg = ''
        if obj == 'Tags':
            if erase == 'true':
                msg += Tag.eraseAll()
            if load == 'true':
                msg += Tag.load( overwrite )
        elif obj == 'Colors':
            if erase == 'true':
                msg += Color.eraseAll()
            if load == 'true':
                msg += Color.load( overwrite )
        elif obj == 'Markers':
            if erase == 'true':
                msg += Marker.eraseAll()
            if load == 'true':
                msg += Marker.load( overwrite )
        elif obj == 'Users':
            msg = '<br />Users must be managed via the admin interface.'
        elif obj == 'Pages':
            if erase == 'true':
                msg += GWPage.eraseAll()
                GWQuery.eraseAll()
            if load == 'true':
                msg += GWPage.load( overwrite )
        elif obj == 'Features':
            if erase == 'true':
                msg += GWFeature.eraseAll()
                GWQuery.eraseAll()
            if load == 'true':
                msg += GWFeature.load( overwrite )
        elif obj == 'Comments':
            if erase == 'true':
                msg += GWComment.eraseAll()
            if load == 'true':
                msg += GWComment.load( overwrite )
        else:
            msg = 'Unknown object.'
        self.response.headers['Content-Type'] = 'application/javascript'
        self.response.out.write( msg )
        self.response.out.write( '<br />')
        
        
        
        